-module(fib_server).

-behavior(gen_server).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3,
		terminate/2]).

-export([start_link/0, fib/1, fib2/1]).

-record(cache, {first, items}).

% external API
start_link() ->
	gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

fib(N) ->
	gen_server:call(?MODULE, {fib, N}).

fib2(0) -> 0;
fib2(1) -> 1;
fib2(N) when N > 1 ->
	fib2(N, 1, {1, 0}).

fib2(N, X, {I1, I2}) when N > X ->
	fib2(N, X+1, {I1+I2, I1});

fib2(N, N, {I1, _}) ->
	I1.

% internal API
init_cache() ->
	#cache{first=1,items=[1, 0]}.

calc_next(#cache{first=First, items=[I1, I2 | _] = Items}) ->
	#cache{first=First+1, items=[I1+I2|Items]}.

calc(Val, #cache{first=First} = Cache) when First >= Val ->
	{lists:nth(First - Val + 1, Cache#cache.items), Cache};

calc(Val, #cache{} = Cache) when Val > 1 ->
	calc(Val, calc_next(Cache)).

% gen_server api: 
init([]) ->
	{ok, init_cache()}.

handle_call({fib, N} = _Call, _From, State) ->
	{Reply, Newstate} = calc(N, State),
	{reply, {ok, Reply}, Newstate}.

% will throw bad match almost certainly...
handle_cast({} = _Msg, State) ->
	{noreply, State}.

handle_info({} = _Info, State) ->
	{noreply, State}.

code_change(_OldVsn, State, _Extra) ->
	{ok, State}.

terminate(Reason, State) ->
	ok.
